import math, random
import pyglet
from pyglet.gl import *
from pyglet.window import key, mouse
import cocos
from cocos import menu, director, layer, sprite, tiles, actions
import data

def texture_set_mag_filter_nearest( texture ):
    glBindTexture( texture.target, texture.id )
    glTexParameteri( texture.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST )
    glBindTexture( texture.target, 0 )

def main():
    director.director.init( width=480, height=272, vsync=True, 
        resizable=True )
    window = director.director.window
    #director.director.show_FPS = True
    pyglet.clock.set_fps_limit( 60 )
    #pyglet.gl.glClearColor( .3, .3, .3, 1 )

    menu_layer = GameMenu()
    main_scene = cocos.scene.Scene( menu_layer )
    director.director.run( main_scene )


class GameMenu( menu.Menu ):
    def __init__( self ):
        super( GameMenu, self ).__init__()
        selections = []
        selections.append( menu.MenuItem( 'Start', self.on_start) )
        selections.append( menu.MenuItem('Quit', self.on_quit) )
        self.create_menu( selections )
        self.player = pyglet.media.Player()
        self.player.eos_action = pyglet.media.Player.EOS_LOOP
        if pyglet.media.have_avbin:
            music = pyglet.resource.media( 'py_stage_select_loop.ogg' )
            self.player.queue( music )
        
    def on_enter( self ):
        super( GameMenu, self ).on_enter()
        self.player.play()
            
    def on_exit ( self ):
        super( GameMenu, self ).on_exit()
        self.player.pause()
                    
    def on_start( self ):
        scene = cocos.scene.Scene()
        scroller = tiles.ScrollingManager()
        front_r = tiles.load( 'map.xml' )
        back_r = tiles.load( 'bg_map.xml' )
        for id, tset in front_r.findall( tiles.TileSet ):
            for tile in tset.itervalues():
                texture_set_mag_filter_nearest(
                    tile.image.get_texture() )
        for id, tset in back_r.findall( tiles.TileSet ):
            for tile in tset.itervalues():
                texture_set_mag_filter_nearest(
                    tile.image.get_texture() )
        front_level = front_r['map']
        back_level = back_r['bg_map']
        sl = SpriteLayer( front_level )
        scroller.add( sl, z=2 )
        scroller.add( front_level, z=1 )
        scroller.add( back_level, z=0 ) 
        scene.add( scroller )
        scene.souls_label = cocos.text.Label( 
            'Success: 0/' + str( sl.chief_count ), 
            font_size=20,
            position=(320, 250) )
        scene.add( scene.souls_label, z=200 )
        director.director.push( scene )
        
    def on_quit( self ):
        director.director.pop()
        
        
class SpriteLayer( tiles.ScrollableLayer ):
    is_event_handler = True
    def __init__( self, level ):
        super( SpriteLayer, self ).__init__()
        self.level = level
        self.batchnode = cocos.batch.BatchNode()
        self.add( self.batchnode, z=1 )
        self._setup_level()
        self.chief_count = 0
        self.player = pyglet.media.Player()
        self.player.eos_action = pyglet.media.Player.EOS_LOOP
        if pyglet.media.have_avbin:
            music = pyglet.resource.media( 'py_rasta_cave_loop.ogg' )
            self.player.queue( music )
        for row in level.cells:
            for cell in row:
                if cell.get( 'chief-spawn' ):
                    self.chief_count += 1
            
        self.bindings = { #key constant : button name
            key.LEFT:'left',
            key.RIGHT:'right',
            key.UP:'up',
            key.DOWN:'down',
            key.RCTRL:'fire',
            mouse.LEFT:'left',
            mouse.RIGHT:'right'
        }
        self.buttons = { #button name : current value, 0 not pressed, 1 pressed
            'left':0,
            'right':0,
            'up':0,
            'down':0,
            'fire':0,
        }
        self.schedule( self.update )
        self.chief_sprites = []
        
    def on_enter( self ):
        super( SpriteLayer, self ).on_enter()
        self.player.play()
            
    def on_exit ( self ):
        super( SpriteLayer, self ).on_exit()
        self.player.pause()
            
    def _setup_level( self ):
        window = director.director.window
        cells = self.level.get_in_region( 0, 0, 480, 272 )
        for cell in cells:
            if cell.get( 'player-spawn' ):
                # Gotta fix PlayerSprite to have (0,0) anchor
                ps = PlayerSprite( (cell.x + 32, cell.y + 16) )
                self.add( ps, z=100 )
                
    def update( self, dt ):
        if self.chief_count == 0:
            director.director.replace( WolfScene() )
        cells = self.level.get_visible_cells()
        for cell in cells:
            if cell.get( 'chief-spawn' ):
                # Gotta fix ChiefSprite to have (0,0) anchor
                cs = ChiefSprite( (cell.x + 32, cell.y + 64) ) 
                self.chief_sprites.append( cs ) 
                self.batchnode.add( cs, z=0 )
                del cell.properties['chief-spawn']
                
        
    def on_key_press( self, k, m ):
        binds = self.bindings
        if k == key.UP:
            if self.buttons[ binds[k] ]:
                self.buttons[ binds[k] ] = 0
            else:
                self.buttons[ binds[k] ] = 1
            return True
        if k in binds:
            self.buttons[ binds[k] ] = 1
            return True
        return False

    def on_key_release( self, k, m ):
        binds = self.bindings
        if k != key.UP:
            if k in binds:
                for ky in self.buttons.iterkeys():
                    self.buttons[ky] = 0
            return True
        return False
        
    def on_mouse_press( self, x, y, b, m ):
        binds = self.bindings
        if b in binds:
            self.buttons[ binds[b] ] = 1
            return True
        return False
        
    def on_mouse_release( self, x, y, b, m ):
        binds = self.bindings
        if b in binds:
            for ky in self.buttons.iterkeys():
                self.buttons[ky] = 0
            return True
        return False
        
class WolfScene( cocos.scene.Scene ):
    def __init__( self ):
        super( WolfScene, self ).__init__()
        img = pyglet.resource.image( 'spiritwolf.png' )
        texture_set_mag_filter_nearest( img )
        spirit_wolf = cocos.sprite.Sprite( 
            img, position=(240, 136) )
        self.add( spirit_wolf )
        howl_sound = pyglet.resource.media( 
            'nesSFX-wolf02.wav', streaming=False )
        howl_sound.play()
        self.player = pyglet.media.Player()
        if pyglet.media.have_avbin:
            music = pyglet.resource.media( 'py_wind_zone_loop.ogg' )
            self.player.queue( music )
        
    def on_enter( self ):
        super( WolfScene, self ).on_enter()
        self.player.play()
            
    def on_exit ( self ):
        super( WolfScene, self ).on_exit()
        self.player.pause()
        
        
        
        
class RectMapCollider(object):
    '''
    Modified from cocos.tiles
    '''
    def collide_bottom(self, dy):
        pass

    def collide_left(self, dx):
        pass

    def collide_right(self, dx):
        pass

    def collide_top(self, dy):
        pass

    def do_collision(self, cell, last, rect):
        g = cell.tile.properties.get
        #g = cell.properties.get
        self.resting = False
        is_collision = False
        if (g('top') and last.bottom >= cell.top and rect.bottom < cell.top):
            #print last.bottom, rect.bottom
            if (last.right > cell.left and last.left < cell.right):
                #~ print "last bottom", last.bottom
                #~ print "rect bottom", rect.bottom
                #~ print "cell top", cell.top
                dy = last.y - rect.y
                rect.bottom = cell.top
                if dy: 
                    self.collide_bottom(dy)
                is_collision = True
        if (g('left') and last.right <= cell.left and rect.right > cell.left):
            if (last.bottom < cell.top):
                dx = last.x - rect.x
                rect.right = cell.left
                if dx: 
                    self.collide_right(dx)
                is_collision = True
        if (g('right') and last.left >= cell.right and rect.left < cell.right):
            if (last.bottom < cell.top):
                dx = last.x - rect.x
                rect.left = cell.right
                if dx: 
                    self.collide_left(dx)
                is_collision = True
        if (g('bottom') and last.top <= cell.bottom and rect.top > cell.bottom):
            if (last.right > cell.left and last.left < cell.right):
                dy = last.y - rect.y
                rect.top = cell.bottom
                if dy: 
                    self.collide_top(dy)
                is_collision = True
        return is_collision
        
        
class PlayerSprite( sprite.Sprite, RectMapCollider ):
    def __init__( self, pos ):
        self._load_images()
        self._load_sounds()
        super( PlayerSprite, self ).__init__( 
            self.image_states['crawling'], position=pos )

        self.add( self.rwing, z=-1 )
        self.rwing.O_Y = 10
        self.rwing.O_X = -38
        self.rwing.position = (self.rwing.O_X, self.rwing.O_Y)
        self.rwing.rotation = 10
        
        self.lwing.O_Y = 4
        self.lwing.O_X = -46
        self.add( self.lwing )
        self.lwing.position = (self.lwing.O_X, self.lwing.O_Y)
        
        self.vel_x = 0
        self.vel_y = 0
        self.accel_x = 0
        self.accel_y = 0
        self.DEFAULT_FLYTIME = 5
        self.fly_time_counter = self.DEFAULT_FLYTIME
        
        self.rect = cocos.rect.Rect( 
            self.x - self.width / 2 + 2, self.y - self.height / 2 + 2, 
            self.width - 4, self.height - 4 )
        #self.batchnode.add( self )
            
        self.state = None
        self.is_flipped_right = False
        self._flip_left( False )
        self.particles = []
        self.schedule( self.update )
        
        self.anchor = (32, 16) # why was this 0,0?

    def _load_images( self ):
        images = []
        images.append( pyglet.resource.image('longicorn0.png') )
        images.append( pyglet.resource.image('longicorn1.png') )
        images.append( pyglet.resource.image('longicorn2.png') )
        for img in images:
            img.anchor_x = img.width / 2
            img.anchor_y = img.height / 2
            texture_set_mag_filter_nearest( img.get_texture() )
        self.image_states = {}
        self.image_states['crawling'] = \
            pyglet.image.Animation.from_image_sequence( images, .3 )
            
        stopped_image = pyglet.resource.image( 'longicorn5.png' )
        stopped_image.anchor_x = stopped_image.width / 2
        stopped_image.anchor_y = stopped_image.height / 2
        texture_set_mag_filter_nearest( stopped_image.get_texture() )
        self.image_states['stopped'] = stopped_image
        
        flying_image = pyglet.resource.image( 'longicorn3.png' )
        flying_image.anchor_x = flying_image.width / 2
        flying_image.anchor_y = flying_image.height / 2
        texture_set_mag_filter_nearest( flying_image.get_texture() )
        self.image_states['flying'] = flying_image
            
        rwing_image = pyglet.resource.image('feather_horn.png')
        texture_set_mag_filter_nearest( rwing_image.get_texture() )
        #~ lwing_image = rwing_image.get_texture().get_transform( 
            #~ flip_x=True )
        lwing_image = rwing_image
        self.rwing = sprite.Sprite( rwing_image )
        self.lwing = sprite.Sprite( lwing_image )
        
    def _load_sounds( self ):
        self.sounds = {}
        self.sounds['flap'] = pyglet.resource.media(
            'nesSFX-flap02.wav', streaming=False)
        self.sounds['stopped'] = pyglet.resource.media(
            'nesSFX-cockroach01.wav', streaming=False)


    def _flip_left( self, flip_left ):
        if self.state is not 'flying':
            if flip_left and self.is_flipped_right:
                self.image = self.image.get_transform( flip_x=True )
                self.image_states['crawling'] = \
                    self.image_states['crawling'].get_transform(
                        flip_x=True )
                self.image_states['stopped'] = \
                    self.image_states['stopped'].get_texture(
                        ).get_transform( flip_x=True )
                        
                self.lwing.image = self.lwing.image.get_transform( 
                    flip_x=True )
                self.lwing.x += 26
                self.rwing.image = self.rwing.image.get_transform( 
                    flip_x=True )
                self.rwing.x += 10
                self.rwing.rotation = -9
                
                self.is_flipped_right = False
                
            elif not flip_left and not self.is_flipped_right:
                self.image = self.image.get_transform( flip_x=True )
                self.image_states['crawling'] = \
                    self.image_states['crawling'].get_transform(
                        flip_x=True )
                self.image_states['stopped'] = \
                    self.image_states['stopped'].get_texture(
                        ).get_transform( flip_x=True )
                        
                self.lwing.image = self.lwing.image.get_transform( 
                    flip_x=True )
                self.lwing.x = self.lwing.O_X
                self.rwing.image = self.rwing.image.get_transform( 
                    flip_x=True )
                self.rwing.x = self.rwing.O_X
                self.rwing.rotation = 10
                    
                self.is_flipped_right = True
                
    def _set_state( self, state ):
        if state != self.state:
            self.lwing.stop()
            self.rwing.stop()
            self.image = self.image_states[state]
            if self.state is 'flying' or state is 'flying':
                try:
                    self.sounds[state].play()
                except KeyError:
                    pass
                self.lwing.rotation = 0
                self.rwing.rotation = 0
                if isinstance(self.image, pyglet.image.Animation):
                    w = self.image.frames[0].image.width
                    h = self.image.frames[0].image.height
                else: 
                    w, h = self.image.width, self.image.height
                
                self.anchor = (w / 2, h / 2 )
                last = self.rect.copy()
                self.rect = cocos.rect.Rect( 
                    self.x - self.width / 2 + 2, 
                    self.y - self.height / 2 + 2, 
                    self.width - 4, 
                    self.height - 4 )
                x, y = self.rect.position
                
                self._flip_left( False )
                self.rwing.image = self.rwing.image.get_transform( 
                    flip_x=True )
                    
                if state is 'flying':
                    self.rwing.rotation = 0
                    self.lwing.image_anchor = (64, 0)
                    self.rwing.image_anchor = (0, 0)
                    self.remove( self.rwing )
                    self.add( self.rwing, z=2 )
                    self.lwing.position = (-20, -20)
                    self.rwing.position = (-13, -20)
                else:
                    w, h = self.lwing.width, self.lwing.height
                    self.rwing.rotation = 10
                    self.lwing.image_anchor = (w / 2, h / 2)
                    self.rwing.image_anchor = (w / 2, h / 2)
                    self.remove( self.rwing )
                    self.add( self.rwing, z=-1 )
                    self.lwing.position = (self.lwing.O_X, self.lwing.O_Y)
                    self.rwing.position = (self.rwing.O_X, self.rwing.O_Y)
                      
                # Check for collisions
                self.rect.set_position( (x, y) )
                cells = self.parent.level.get_in_region(
                    x, y, x+self.width, y+self.height )
                for cell in cells:
                    if cell is not None and cell.tile is not None:
                        self.do_collision( cell, last, self.rect )
                self.position = self.rect.position

            self.state = state
        
    def collide_bottom( self, dy ):
        #~ if self.state is 'flying':
            #~ self._set_state( 'stopped' )
        self.fly_time_counter = self.DEFAULT_FLYTIME

    def collide_left( self, dx ):
        pass #print "left"

    def collide_right( self, dx ):
        pass #print "right"

    def collide_top( self, dy ):
        self.vel_y = 0
    
    def _flap( self, wing ):
        rotate = actions.interval_actions.RotateBy( -120, .2 )
        if wing is self.lwing:
            self.lwing.rotation = 0
            self.lwing.stop()
            self.lwing.do( rotate + actions.Reverse( rotate ) )
        elif wing is self.rwing:
            self.rwing.rotation = 0
            self.rwing.stop()
            self.rwing.do( actions.Reverse( rotate ) + rotate )
            
    def _emit_dust( self, wing ):
        batch = self.parent.batchnode
        nparticles = 10
        if len( self.particles ) > 100:
            for n in range( nparticles ):
                p = self.particles.pop( 0 )
                batch.remove( p )
                del p
                
        x = wing.x + self.x
        
        # ugh why are these wing positions so weird, flipping?
        if wing is self.rwing:
            x += wing.width - wing.x
        elif wing is self.lwing:
            x -= wing.x
            
        y = wing.y + self.y + wing.height
        w, h = wing.height, wing.width
        for n in range( nparticles ):
            spread = random.randrange( 0, 64 )
            particle = ParticleSprite( position=(x-spread, y) )
            batch.add( particle, z=100 )
            self.particles.append( particle )
            
        
    def update( self, dt ):
        btns = self.parent.buttons        
        if btns['left'] and btns['right']:
            self.vel_x = 0
            if self.state is not 'flying':
                self._set_state( 'flying' )
            self._flap( self.lwing )
            self._flap( self.rwing )
            if self.fly_time_counter > 0:
                self._emit_dust( self.rwing )
                self._emit_dust( self.lwing )

                self.accel_y = self.fly_time_counter * 60
                self.fly_time_counter -= 1
                btns['left'] = 0
                btns['right'] = 0
                self.sounds['flap'].play()
            else:
                self.fly_time_counter = 0
        #~ elif btns['left'] and self.state is 'flying':
            #~ self._flap( self.lwing )
            #~ self.accel_x = 2
            #~ btns['left'] = 0
            #~ if self.fly_time_counter > 0:
                #~ self._emit_dust( self.lwing )
                #~ self.accel_y = self.fly_time_counter / 3.0
                #~ self.fly_time_counter -= .5
                #~ self.sounds['flap'].play()
            #~ else:
                #~ self.fly_time_counter = 0
#~ 
        #~ elif btns['right'] and self.state is 'flying':
            #~ self._flap( self.rwing )
            #~ self.accel_x = -2
            #~ btns['right'] = 0
            #~ if self.fly_time_counter > 0:
                #~ self._emit_dust( self.rwing )
                #~ self.accel_y = self.fly_time_counter / 3.0
                #~ self.fly_time_counter -= .5
                #~ self.sounds['flap'].play()
            #~ else:
                #~ self.fly_time_counter = 0
        else:
            if self.state is not 'flying':
                self.accel_x = 0
                vel_x = 60.0 #units per second
                self.vel_x = (btns['right'] - btns['left']) * vel_x * dt
                if btns['left']:
                    self._flip_left( True )
                    self._set_state( 'crawling' )
                elif btns['right']:
                    self._flip_left( False )
                    self._set_state( 'crawling' )
                else:
                    self._set_state( 'stopped' )
                
                rwy = self.rwing.O_Y
                lwy = self.lwing.O_Y
                if isinstance( self.image, pyglet.image.Animation ):
                    i = self._frame_index
                    if i == 0:
                        self.lwing.y = lwy
                        self.rwing.y = rwy
                    elif i == 1:
                        self.lwing.y = lwy - 3
                        self.rwing.y = rwy - 3
                    elif i == 2:
                        self.lwing.y = lwy + 1
                        self.rwing.y = rwy + 1
                else:
                    self.lwing.y = lwy
                    self.rwing.y = rwy
            #~ else:
                #~ if abs( self.vel_x ) > 1:
                    #~ self.accel_x = cmp( self.vel_x, 0 ) * -.5
                #~ else:
                    self.accel_x = 0

            self.accel_y = -.05 * 60
            
        self.vel_y += self.accel_y
        self.vel_x += self.accel_x


        #~ # Limit the max velocity
        #~ MAX_HORIZ_VEL = 3
        #~ MAX_DOWN_VEL = 3
        #~ MAX_UP_VEL = 3
        #~ if self.vel_y < -MAX_DOWN_VEL:
            #~ self.vel_y = -MAX_DOWN_VEL
        #~ if self.vel_y > MAX_UP_VEL:
            #~ self.vel_y = MAX_UP_VEL
        #~ if abs( self.vel_x ) > MAX_HORIZ_VEL:
            #~ self.vel_x = cmp( self.vel_x, 0 ) * MAX_HORIZ_VEL 

        x, y = self.rect.position
        x += self.vel_x
        y += self.vel_y * dt + 0.5 * self.accel_y * dt ** 2
         
        # Check for collisions
        last = self.rect.copy()
        self.rect.set_position( (x, y) )
        cells = self.parent.level.get_in_region(
            x, y, x+self.rect.width, y+self.rect.height )
        is_collision = False
        for cell in cells:
            if cell is not None and cell.tile is not None:
                is_collision = self.do_collision( 
                    cell, last, self.rect )
        #self.position = self.rect.position
                
        x, y = self.rect.get_center()
        # Why do the wings vibrate if floats?
        self.position = (int(x), int(y))
        
        #~ if not is_collision:
            #~ self._set_state( 'flying' )
        self.parent.parent.set_focus( *self.position )
        
        
#~ class PlatformLayer(tiles.ScrollableLayer):
    #~ self.__init__(self):
        #~ super(PlatformLayer, self).__init__(
        
class ParticleSprite( cocos.sprite.Sprite, RectMapCollider ):
    def __init__( self, img='dust.png', *args, **kwargs ):
        super( ParticleSprite, self ).__init__( img, *args, **kwargs )
        texture_set_mag_filter_nearest( self.image ) 
        #self.image = random.choice(self.images)
        self.rect = cocos.rect.Rect( 
            self.x - self.width / 2, self.y - self.height / 2, 
            self.width, self.height )
        angle = math.radians( random.randrange( 180, 360 ) )
        self.vel_x = 10 * math.cos( angle )
        self.vel_y = 2 * math.sin( angle )
        self.schedule( self.update )
        self.schedule_interval( self.check_chief_collision, .5 )
        
    def collide_bottom( self, dy ):
        self.vel_y = 0
        
    def update( self, dt ):
        if self.vel_y != 0:
            last = self.rect.copy()
            cx, cy = self.position
            cx += self.vel_x * dt
            cy += self.vel_y * dt * 50
            self.rect.set_center( (cx, cy) )
            self.vel_y = self.vel_y - 2 * dt
            level = self.parent.parent.level
            cells = level.get_in_region( 
                cx - level.tw, cy - level.th,
                cx + level.tw, cy + level.th )
            is_collision = False
            for cell in cells:
                if cell is not None and cell.tile is not None:
                    is_collision = self.do_collision( 
                        cell, last, self.rect )
            self.position = self.rect.get_center()
        else:
            self.pause_scheduler()
                        
    def check_chief_collision( self, dt ):
        chief_sprites = self.parent.parent.chief_sprites
        for chief in chief_sprites:
            if self.rect.intersects( chief.rect ):
                chief.cowboyify()
                chief_sprites.remove( chief )


class ChiefSprite( cocos.sprite.Sprite, RectMapCollider ):
    def __init__( self, pos ):
        self.cowboy_img = pyglet.resource.image( 'reagan01.png' )
        self.cowboy_img.anchor_x = self.cowboy_img.width / 2
        self.cowboy_img.anchor_y = self.cowboy_img.height / 2
        texture_set_mag_filter_nearest( self.cowboy_img.get_texture() ) 
          
        images = []
        images.append( pyglet.resource.image('INDIAN_cycler01.png') )
        images.append( pyglet.resource.image('INDIAN_cycler02.png') )
        for img in images:
            img.anchor_x = img.width / 2
            img.anchor_y = img.height / 2
            texture_set_mag_filter_nearest( img.get_texture() )
        anim = pyglet.image.Animation.from_image_sequence( images, .2 )
        super( ChiefSprite, self ).__init__( anim, position=pos )
        self.schedule( self.update )
        self.rect = cocos.rect.Rect( 
            self.x - self.width / 2 + 2, self.y - self.height / 2 + 2, 
            self.width - 4, self.height - 4 )
        self.vel_x = 100.0
        self.vel_y = -200.0
        self.sound = pyglet.resource.media( 'nesSFX09.wav', streaming=False )
    
    def collide_left( self, dx ):
        self.image = self.image.get_transform( flip_x = True )
        self.vel_x *= -1
        
    def collide_right( self, dx ):
        self.image = self.image.get_transform( flip_x = True )
        self.vel_x *= -1
        
    def cowboyify( self ):     
        self.image = self.cowboy_img
        self.sound.play()
        layer = self.parent.parent
        layer.chief_count -= 1
        # Hardcoding soul count in a rush
        scene = self.parent.parent.parent.parent
        scene.remove( scene.souls_label )
        scene.souls_label = \
            cocos.text.Label( 
                "Success " + str( 8 - layer.chief_count ) 
                + "/8", font_size=20, position=(320, 250) )
        scene.add( scene.souls_label, z=200 )
        self.pause_scheduler()
        
    def update( self, dt ):
        x, y = self.rect.position
        x += self.vel_x * dt
        y += self.vel_y * dt
        
        # Check for collisions
        last = self.rect.copy()
        self.rect.set_position( (x, y) )
        cells = self.parent.parent.level.get_in_region(
            x, y, x+self.rect.width, y+self.rect.height )
        for cell in cells:
            if cell is not None and cell.tile is not None:
                is_collision = self.do_collision( 
                    cell, last, self.rect )
        self.position = self.rect.get_center()
        

        



